package org.fujene.memcon;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Logger;

import org.fujene.diskblock.DiskBlock;
import org.fujene.diskblock.PureContentDiskBlock;
import org.fujene.diskblock.SkipPointerDiskBlock;
import org.fujene.diskblock.repli.ReplicateContentDiskBlock;
import org.fujene.diskblock.repli.ReplicateDictionaryDiskBlock;
import org.fujene.diskblock.repli.ReplicateInvertIndexDiskBlock;
import org.fujene.memoryblock.MemoryBlock;
import org.fujene.memoryblock.Optimizer;
import org.fujene.memoryblock.PureContentBlock;
import org.fujene.memoryblock.SkipPointerBlock;
import org.fujene.memoryblock.repli.ReplicateContentBlock;
import org.fujene.memoryblock.repli.ReplicateDictionaryBlock;
import org.fujene.memoryblock.repli.ReplicateInvertIndexBlock;
import org.fujene.structure.Constants;
import org.fujene.structure.ContentInfo;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.InvertIndexData;
import org.fujene.structure.LookupPointer;

public class ReplicateIndexMemory{
	private static final int MEM_TYPES = Constants.MEMORY_TYPE_KINDS; // そのうち拡張する予定
	private static final int MEM_DICT = Constants.MEMORY_TYPE_DICT;
	private static final int MEM_INVINDEX = Constants.MEMORY_TYPE_INVINDEX;
	private static final int MEM_SKIPPTR = Constants.MEMORY_TYPE_SKIPPTR;
	private static final int MEM_CONT = Constants.MEMORY_TYPE_CONTENTS;
	private static final int MEM_SKIPCONT = Constants.MEMORY_TYPE_SKIPPTRCONT;
	private static final int MEM_PURECONT = Constants.MEMORY_TYPE_PURECONTENT;
	private static final int DEFAULT_OPT_LEN = 2;

	// クラスメンバ ブロックをなんとか表す

	// ブロック本体
	// 各ブロックのサイズ
	private int blockSize = 1048576; // 1MB
	// ブロック数
	private int blockNum = 256;

	private MemoryBlock[] blocks = null;
	private int[] writeBackIDs = null;
	private int nextID = 0;

	private int[] firstBlock = null;
	private int[] lastBlock = null;

	private int contentNum;
	private int supplementNum;
	private int[] contentFirst;
	private int[] contentLast;
	private int[] contentSkipPtrFirst;
	private int[] contentSkipPtrLast;
	private int[] supplementFirst;
	private int[] supplementLast;
	// ディスクとの連絡用
	public IndexDisk disk = null;
	public String dir = null;

	public ContentInfo contentInfo = null;
	private Logger logger = null;

	public ReplicateIndexMemory(int blockSize, int blockNum, IndexDisk disk, ContentInfo contentInfo,
			Logger logger){
		setBlockSize(blockSize);
		setBlockNum(blockNum);
		this.disk = disk;
		this.dir = disk.getDir();
		this.contentNum = contentInfo.getContentNum();
		this.contentFirst = new int[this.contentNum];
		this.contentLast = new int[this.contentNum];
		this.contentSkipPtrFirst = new int[this.contentNum];
		this.contentSkipPtrLast = new int[this.contentNum];
		this.supplementNum = contentInfo.getAppendixNum();
		this.supplementFirst = new int[this.supplementNum];
		this.supplementLast = new int[this.supplementNum];
		this.contentInfo = contentInfo;
		this.logger = logger;
		initBlock();
	}

	// 一番最初に使おう
	private void initBlock(){
		this.blocks = new MemoryBlock[this.blockNum];
		this.writeBackIDs = new int[MEM_TYPES];

		this.firstBlock = new int[MEM_TYPES];
		this.lastBlock = new int[MEM_TYPES];

		for(int i = 0; i < MEM_TYPES; i++){
			this.firstBlock[i] = -1;
			this.lastBlock[i] = -1;
			this.writeBackIDs[i] = -1;
		}

		for(int i = 0; i < this.contentNum; i++){
			this.contentFirst[i] = -1;
			this.contentLast[i] = -1;
			this.contentSkipPtrFirst[i] = -1;
			this.contentSkipPtrLast[i] = -1;
		}

		for(int i = 0; i < this.supplementNum; i++){
			this.supplementFirst[i] = -1;
			this.supplementLast[i] = -1;
		}

		initializeAllocate();
	}

	// 初期アロケート 種類追加時はここをいじる
	private void initializeAllocate(){
		int i;
		allocateBlock(MEM_DICT, 0);
		allocateBlock(MEM_SKIPPTR, 0);
		allocateBlock(MEM_INVINDEX, 0);
		for(i = 0; i < contentNum; i++)
			allocateBlock(MEM_SKIPCONT, i);
		for(i = 0; i < contentNum; i++)
			allocateBlock(MEM_CONT, i);
		for(i = 0; i < supplementNum; i++)
			allocateBlock(MEM_PURECONT, i);
	}

	private int allocateBlock(int type, int index){
		for(int i = 0; i < blocks.length; i++){
			if(i == blocks.length - 1 && blocks[i] != null){
				int mover = -1;
				// メモリの空きがなかったのでディスクに寄せる
				// 優先順: PureContents -> Contents -> Inv. Index ->
				// (→はしばらく凍結)Dictionary ->
				// Skip ptr.
				if(getMemoryAmount(MEM_PURECONT) > 2){
					mover = getMinimumBlock(MEM_PURECONT);
					saveMemory(mover);
					i = mover;
				}else if(getMemoryAmount(MEM_CONT) > 2){
					mover = getMinimumBlock(MEM_CONT);
					saveMemory(mover);
					i = mover;
				}else if(getMemoryAmount(MEM_INVINDEX) > 2){
					mover = getMinimumBlock(MEM_INVINDEX);
					saveMemory(mover);
					i = mover;
				}
			}

			if(blocks[i] == null){
				switch(type){
				case MEM_DICT:
					blocks[i] = new ReplicateDictionaryBlock(blockSize, nextID++, true);
					if(firstBlock[MEM_DICT] == -1) firstBlock[MEM_DICT] = nextID - 1;
					lastBlock[MEM_DICT] = nextID - 1;
					blocks[i].setOption(1, contentNum);
					break;
				case MEM_SKIPPTR:
					blocks[i] = new SkipPointerBlock(blockSize, nextID++, true);
					if(firstBlock[MEM_SKIPPTR] == -1)
						firstBlock[MEM_SKIPPTR] = nextID - 1;
					else blocks[lastBlock[MEM_SKIPPTR]].setOption(1, nextID - 1);
					lastBlock[MEM_SKIPPTR] = nextID - 1;
					blocks[i].setOption(1, -1);
					blocks[i].setOption(2, MEM_SKIPPTR);
					lastBlock[MEM_SKIPPTR] = i;
					break;
				case MEM_SKIPCONT:
					blocks[i] = new SkipPointerBlock(blockSize, nextID++, true);
					if(contentSkipPtrFirst[index] == -1)
						contentSkipPtrFirst[index] = nextID - 1;
					else blocks[contentSkipPtrLast[index]].setOption(1, nextID - 1);
					contentSkipPtrLast[index] = nextID - 1;
					blocks[i].setOption(1, -1);
					blocks[i].setOption(2, MEM_SKIPCONT);
					contentSkipPtrLast[index] = nextID - 1;
					break;
				case MEM_INVINDEX:
					blocks[i] = new ReplicateInvertIndexBlock(blockSize, nextID++, true);
					if(firstBlock[MEM_INVINDEX] == -1) firstBlock[MEM_INVINDEX] = nextID - 1;
					lastBlock[MEM_INVINDEX] = nextID - 1;
					break;
				case MEM_CONT:
					blocks[i] = new ReplicateContentBlock(blockSize, nextID++, true);
					if(contentFirst[index] == -1) contentFirst[index] = nextID - 1;
					contentLast[index] = nextID - 1;
					blocks[i].setOption(1, contentInfo.getConnectionNum(index));
					break;
				case MEM_PURECONT:
					blocks[i] = new PureContentBlock(blockSize, nextID++, true);
					if(supplementFirst[index] == -1) supplementFirst[index] = nextID - 1;
					supplementLast[index] = nextID - 1;
					break;
				default:
					// タイプ外
					throw new IndexOutOfBoundsException();
				}

				return i;
			}
		}

		return -1;
	}

	private int getMemoryAmount(int type){
		int amount = 0;

		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null && blocks[i].getType() == type) amount++;
		}

		return amount;
	}

	private int getBlockArrayIndex(int ID){
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] == null) continue;
			if(blocks[i].getID() == ID) return i;
		}
		// なかった
		return -1;
	}

	public ExtendTerm lookupDictionary(LookupPointer ptr, int valueType){
		if(ptr == null || ptr.equals(Constants.EMPTY_PTR)) return null;
		int arrayIndex = getBlockArrayIndex(ptr.block);
		if(arrayIndex != -1){
			return ((ReplicateDictionaryBlock)blocks[arrayIndex]).getTerm(ptr.point, valueType);
		}else{
			// ディスク探索
			if(disk.isExist(ptr.block)){
				try{
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk.getFileMap()
							.get(Integer.valueOf(ptr.block)));
					return dicDisk.getTerm(ptr.point, valueType);
				}catch(Exception e){}
			}
		}

		return null;

	}

	// 辞書ポインタだけ読み込み
	public LookupPointer lookupDictionaryNext(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateDictionaryBlock)blocks[arrayIndex]).getNextPointer(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk.getFileMap()
							.get(Integer.valueOf(ptr.block)));
					return dicDisk.getNextPointer(ptr.point);
				}catch(Exception e){}
			}
		}

		return null;
	}

	// 転置インデックスポインタだけ読み込み
	public LookupPointer lookupDictionaryInvIndex(LookupPointer ptr, int valueType){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateDictionaryBlock)blocks[arrayIndex]).getInvIndexPointer(ptr.point, valueType);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk.getFileMap()
							.get(Integer.valueOf(ptr.block)));
					return dicDisk.getDocIDPointer(valueType, ptr.point);
				}catch(Exception e){}
			}
		}

		return null;
	}

	public long lookupDictionaryFirstID(LookupPointer ptr, int valueType){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateDictionaryBlock)blocks[arrayIndex]).getFirstID(ptr.point, valueType);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					ReplicateDictionaryDiskBlock dicDisk = new ReplicateDictionaryDiskBlock(disk.getFileMap()
							.get(Integer.valueOf(ptr.block)));
					return dicDisk.getFirstID(ptr.point, valueType);
				}catch(Exception e){}
			}
		}

		return 0;
	}

	public LookupPointer storeDictionary(String str, LookupPointer next) throws UnsupportedEncodingException{
		int dictLast = getBlockArrayIndex(lastBlock[MEM_DICT]);

		ReplicateDictionaryBlock dicBlock = (ReplicateDictionaryBlock)blocks[dictLast];
		// 置き場探し
		int newPtr = dicBlock.getNewPointer(str);
		if(newPtr == -1){
			// 他をあたる
			int nextBlock = allocateBlock(MEM_DICT, 0);
			if(nextBlock != -1){
				dictLast = nextBlock;
				dicBlock = (ReplicateDictionaryBlock)blocks[nextBlock];
				newPtr = dicBlock.getNewPointer(str);
			}
		}

		dicBlock.setTerm(newPtr, next, str);
		writeBackIDs[MEM_DICT]++;
		return new LookupPointer(dicBlock.getID(), newPtr);
	}

	// ※追加しない
	public void storeDictionaryPointer(LookupPointer dicPtr, LookupPointer next){
		int arrayIndex = getBlockArrayIndex(dicPtr.block);

		if(arrayIndex != -1){
			try{
				((ReplicateDictionaryBlock)blocks[arrayIndex]).setNextPointer(dicPtr.point, next);
			}catch(Exception e){}
		}else{
			// TODO: ファイル書き換え対応
		}
	}

	// ※追加しない
	public void incrementDocFreq(LookupPointer dicPtr){
		int arrayIndex = getBlockArrayIndex(dicPtr.block);

		if(arrayIndex != -1){
			try{
				((ReplicateDictionaryBlock)blocks[arrayIndex]).incrementDocfreq(dicPtr.point);
			}catch(Exception e){}
		}else{
			// TODO: ファイル書き換え対応
		}
	}

	// ※上書き
	public void storeDictionaryToInvIndex(LookupPointer dicPtr, LookupPointer invIndex, long firstID,
			int valueType){
		int arrayIndex = getBlockArrayIndex(dicPtr.block);

		if(arrayIndex != -1){
			try{
				((ReplicateDictionaryBlock)blocks[arrayIndex]).setInvIndex(dicPtr.point, invIndex, firstID,
						valueType);
			}catch(Exception e){}
		}else{
			// TODO: ファイル書き換え対応
		}
	}

	public LookupPointer lookupSkipPointer(int index){
		int nextBlockNum = firstBlock[MEM_SKIPPTR];
		SkipPointerBlock skipBlock = null;
		int blockIndex = getBlockArrayIndex(nextBlockNum);
		int findLength = 0;
		int length = 0;

		// ブロック探索
		skipBlock = (SkipPointerBlock)blocks[blockIndex];
		// スキップポインタなし
		if(skipBlock.getLength() == 0) return null;

		// インデックス探し
		do{
			length = skipBlock.getLength();
			findLength += length;
			if(findLength >= index) break;
			nextBlockNum = skipBlock.getNextblock();
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
		}while(nextBlockNum != -1);

		// そんなになかった
		if(findLength < index || skipBlock == null) return null;

		index -= (findLength - length);
		return skipBlock.getPointer(index);
	}

	public LookupPointer lookupContentsSkipPointer(int index, int valueType){
		int nextBlockNum = contentSkipPtrFirst[valueType];
		SkipPointerBlock skipBlock = null;
		int blockIndex = getBlockArrayIndex(nextBlockNum);
		int findLength = 0;
		int length = 0;

		// ブロック探索
		skipBlock = (SkipPointerBlock)blocks[blockIndex];
		// スキップポインタなし
		if(skipBlock.getLength() == 0) return null;

		// インデックス探し
		do{
			length = skipBlock.getLength();
			findLength += length;
			if(findLength >= index) break;
			nextBlockNum = skipBlock.getNextblock();
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
		}while(nextBlockNum != -1);

		// そんなになかった
		if(findLength < index || skipBlock == null) return null;

		index -= (findLength - length);
		return skipBlock.getPointer(index);
	}

	public LookupPointer[] lookupPureContentPointer(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateContentBlock)blocks[arrayIndex]).getSupplementPointer(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File desc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contentDisk = new ReplicateContentDiskBlock(ptr.block, desc,
							blockSize, DEFAULT_OPT_LEN);
					return contentDisk.getSupplementPointer(ptr.point);
				}catch(Exception e){}
			}
		}

		return null;
	}

	// 単語スキップポインタのみ
	public int getSkipPointerLength(){
		int nextBlockNum = getBlockArrayIndex(firstBlock[MEM_SKIPPTR]);
		SkipPointerBlock skipBlock = null;
		int blockIndex = -1;
		int findIndex = 0;

		// ブロック探索
		do{
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
			int length = skipBlock.getLength();
			findIndex += length;
			nextBlockNum = skipBlock.getNextblock();
		}while(nextBlockNum != -1);

		return findIndex;
	}

	// 本文スキップポインタのみ
	public int getSkipContentsLength(int type){
		if(type >= contentNum) return -1;
		int nextBlockNum = contentSkipPtrFirst[type];
		SkipPointerBlock skipBlock = null;
		int blockIndex = -1;
		int findIndex = 0;

		// ブロック探索
		do{
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
			int length = skipBlock.getLength();
			findIndex += length;
			nextBlockNum = skipBlock.getNextblock();
		}while(nextBlockNum != -1);

		return findIndex;
	}

	// 本文スキップポインタ対応
	public void storeSkipPointer(int index, int type, LookupPointer ptr, boolean isModify, int indextype){
		try {
		int nextBlockNum;
		if(indextype == MEM_SKIPPTR)
			nextBlockNum = firstBlock[MEM_SKIPPTR];
		else if(indextype == MEM_SKIPCONT)
			nextBlockNum = contentSkipPtrFirst[type];
		else return;
		int blockIndex = -1;
		int findIndex = 0;
		int setIndex = index;
		SkipPointerBlock skipBlock = null;
		SkipPointerBlock setBlock = null;

		// 置き場探し
		do{
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
			int length = skipBlock.getLength();
			findIndex += length;
			if(findIndex - index >= 0){
				setBlock = skipBlock;
				break;
			}
			setIndex -= length;
			nextBlockNum = skipBlock.getNextblock();
		}while(nextBlockNum != -1);

		if(setBlock == null) {
			blockIndex = allocateBlock(indextype, type);
			setBlock = (SkipPointerBlock)blocks[blockIndex];
		}
		
		if(setBlock != null){
			if(isModify) {
//				if(indextype == MEM_SKIPCONT)
//				System.out.println("Modify at " + new LookupPointer(setBlock.getID(), setIndex * 6));
				setBlock.setPointer(setIndex, ptr, isModify);
			}else{
				byte[] moveVal = null;
//				if(indextype == MEM_SKIPCONT)
//					System.out.println("Add at " + new LookupPointer(setBlock.getID(), setIndex * 6));
				moveVal = setBlock.setPointer(setIndex, ptr, isModify);
				if(moveVal != null){
					do{
						nextBlockNum = skipBlock.getNextblock();
						if(nextBlockNum == -1) 
							// あたらしいのをつくる
							blockIndex = allocateBlock(indextype, type);
						else
							blockIndex = getBlockArrayIndex(nextBlockNum);
						setBlock = (SkipPointerBlock)blocks[blockIndex];
//						if(indextype == MEM_SKIPCONT)
//							System.out.println("Move at " + new LookupPointer(setBlock.getID(), setIndex * 6));
						moveVal = setBlock.movePointerRep(moveVal);
					}while(moveVal != null);
				}
			}
		}

		writeBackIDs[type]++;
		}catch (Exception e) {
			e.printStackTrace();
		}
	}

	// デバッグ用 スキップポインタ一覧を出力
	// 本文スキップポインタ対応
	public void showSkipPointer(int type){
		int nextBlockNum = firstBlock[type];
		SkipPointerBlock skipBlock = null;
		int blockIndex = -1;
		LookupPointer output = null;

		// ブロック探索
		do{
			blockIndex = getBlockArrayIndex(nextBlockNum);
			skipBlock = (SkipPointerBlock)blocks[blockIndex];
			int length = skipBlock.getLength();
			for(int i = 0; i < length; i++){
				output = skipBlock.getPointer(i);
				int dicBlock = getBlockArrayIndex(output.block);
				ExtendTerm newTerm = ((ReplicateDictionaryBlock)blocks[dicBlock]).getTerm(output.point, 0);
				newTerm.skipptr_index = i;
				logger.fine("Term at skip pointer: " + newTerm.toString());
			}
			nextBlockNum = skipBlock.getNextblock();
		}while(nextBlockNum != -1);
	}

	public LookupPointer lookupNextInvIndex(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateInvertIndexBlock)blocks[arrayIndex]).getInvertIndex(ptr.point).getPrevPtr();
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File desc = disk.getDescriptor(ptr.block);
					ReplicateInvertIndexDiskBlock invDisk = new ReplicateInvertIndexDiskBlock(ptr.block,
							desc, blockSize, 1);
					return invDisk.getInvertIndex(ptr.point).getPrevPtr();
				}catch(Exception e){}
			}
		}

		return null;
	}

	public InvertIndexData lookupHashID(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateInvertIndexBlock)blocks[arrayIndex]).getInvertIndex(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File desc = disk.getDescriptor(ptr.block);
					ReplicateInvertIndexDiskBlock invDisk = new ReplicateInvertIndexDiskBlock(ptr.block,
							desc, blockSize, 1);
					return invDisk.getInvertIndex(ptr.point);
				}catch(Exception e){}
			}
		}

		return null;
	}

	public LookupPointer storeInvertIndex(LookupPointer prev, long diffID, int termfreq){
		int invertLast = getBlockArrayIndex(lastBlock[MEM_INVINDEX]);
		

		ReplicateInvertIndexBlock invBlock = (ReplicateInvertIndexBlock)blocks[invertLast];
		// 置き場探し
		int left = invBlock.getLeft();
		if(left == 0){
			// 他をあたる
			int nextBlock = allocateBlock(MEM_INVINDEX, 0);
			if(nextBlock != -1){
				invertLast = nextBlock;
				invBlock = (ReplicateInvertIndexBlock)blocks[nextBlock];
				left = invBlock.getLeft();
			}
		}
		writeBackIDs[MEM_INVINDEX]++;

		LookupPointer retPtr = invBlock.setInvertIndex(prev, diffID, termfreq);
		return retPtr;
	}

	public String lookupContents(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateContentBlock)blocks[arrayIndex]).getContent(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					if(disk.deletedContentsInDisk.contains(ptr))
						return Constants.DELETED;
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block, fileDesc,
							blockSize, DEFAULT_OPT_LEN);
					return contDisk.getContent(ptr.point);
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		}

		return null;
	}

	public IndexQueue lookupContentsWithID(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateContentBlock)blocks[arrayIndex]).getContentWithID(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block, fileDesc,
							blockSize, DEFAULT_OPT_LEN);
					return contDisk.getContentWithID(ptr.point);
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		}

		return null;
	}

	public void deleteContents(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			((ReplicateContentBlock)blocks[arrayIndex]).delete(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					disk.deletedContentsInDisk.add(ptr);
				}catch(Exception e){}
			}
		}

	}

	public long lookupContentID(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateContentBlock)blocks[arrayIndex]).getContentID(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block, fileDesc,
							blockSize, DEFAULT_OPT_LEN);
					return contDisk.getContentID(ptr.point);
				}catch(Exception e){}
			}
		}

		return -1;
	}

	public LookupPointer lookupNextContentPointer(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((ReplicateContentBlock)blocks[arrayIndex]).getNextContentPointer(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File fileDesc = disk.getDescriptor(ptr.block);
					ReplicateContentDiskBlock contDisk = new ReplicateContentDiskBlock(ptr.block, fileDesc,
							blockSize, DEFAULT_OPT_LEN);
					return contDisk.getNextContentPointer(ptr.point);
				}catch(Exception e){}
			}
		}

		return null;
	}

	public LookupPointer storeContents(String content, long ID, int valueType, LookupPointer next,
			LookupPointer[] supp){
		int lastContent = getBlockArrayIndex(contentLast[valueType]);

		ReplicateContentBlock contBlock = (ReplicateContentBlock)blocks[lastContent];
		// 置き場探し
		int newPtr = contBlock.getNewPointer(content);
		if(newPtr == -1){
			// 他をあたる
			int nextBlock = allocateBlock(MEM_CONT, valueType);
			if(nextBlock != -1){
				lastContent = nextBlock;
				contBlock = (ReplicateContentBlock)blocks[nextBlock];
				newPtr = contBlock.getNewPointer(content);
			}
		}

		contBlock.setContent(newPtr, content, ID, next, supp);
		writeBackIDs[MEM_CONT]++;
		return new LookupPointer(contBlock.getID(), newPtr);
	}

	/**
	 * 上書きします。
	 * @param ptr
	 * @param next
	 */
	public void storeContentNext(LookupPointer ptr, LookupPointer next){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			((ReplicateContentBlock)blocks[arrayIndex]).setContentPointer(ptr.point, next);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					// TODO: ディスク書き換え
				}catch(Exception e){}
			}
		}
	}

	public String lookupPureContent(LookupPointer ptr){
		int arrayIndex = getBlockArrayIndex(ptr.block);

		if(arrayIndex != -1){
			return ((PureContentBlock)blocks[arrayIndex]).getContent(ptr.point);
		}else{
			if(disk.isExist(ptr.block)){
				try{
					File fileDesc = disk.getDescriptor(ptr.block);
					PureContentDiskBlock contDisk = new PureContentDiskBlock(ptr.block, fileDesc, blockSize,
							DEFAULT_OPT_LEN);
					return contDisk.getContent(ptr.point);
				}catch(Exception e){}
			}
		}

		return null;
	}

	public LookupPointer storePureContent(String str, int index){
		int contentLast = getBlockArrayIndex(supplementLast[index]);

		PureContentBlock contBlock = (PureContentBlock)blocks[contentLast];
		// 置き場探し
		int newPtr = contBlock.getNewPointer(str);
		if(newPtr == -1){
			// 他をあたる
			int nextBlock = allocateBlock(MEM_PURECONT, 0);
			if(nextBlock != -1){
				contentLast = nextBlock;
				contBlock = (PureContentBlock)blocks[nextBlock];
				newPtr = contBlock.getNewPointer(str);
			}
		}

		contBlock.setContent(newPtr, str);
		writeBackIDs[MEM_PURECONT]++;

		return new LookupPointer(contBlock.getID(), newPtr);
	}

	public void readMemory() throws Exception{
		// 記録を読み出し
		// コピーコンストラクタ
		SortedMap<Integer, File> fileMap = new TreeMap<Integer, File>(disk.getFileMap());
		Set<Integer> fileNums = fileMap.keySet();

		System.out.print("Reading memory... ");
		for(Integer file: fileNums){
			int ID = file.intValue();
			int type = disk.getFileType(ID);
			int newID;
			switch(type){
			case MEM_DICT:
				ReplicateDictionaryDiskBlock dicBlock = new ReplicateDictionaryDiskBlock(fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if(newID == -1) newID = allocateBlock(MEM_DICT, 0);
				dicBlock.readToMemory((ReplicateDictionaryBlock)blocks[newID]);
				break;

			case MEM_SKIPPTR:
			case MEM_SKIPCONT:
				SkipPointerDiskBlock skipBlock = new SkipPointerDiskBlock(fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if(newID == -1) newID = allocateBlock(type, 0);
				skipBlock.readToMemory((SkipPointerBlock)blocks[newID]);
				break;

			case MEM_INVINDEX:
				ReplicateInvertIndexDiskBlock invBlock = new ReplicateInvertIndexDiskBlock(fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if(newID == -1) newID = allocateBlock(MEM_INVINDEX, 0);
				invBlock.readToMemory((ReplicateInvertIndexBlock)blocks[newID]);
				break;

			case MEM_CONT:
				ReplicateContentDiskBlock contBlock = new ReplicateContentDiskBlock(fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if(newID == -1) newID = allocateBlock(MEM_CONT, 0);
				contBlock.readToMemory((ReplicateContentBlock)blocks[newID]);
				break;

			case MEM_PURECONT:
				PureContentDiskBlock pureBlock = new PureContentDiskBlock(fileMap.get(file));
				newID = getBlockArrayIndex(ID);
				if(newID == -1) newID = allocateBlock(MEM_CONT, 0);
				pureBlock.readToMemory((ReplicateContentBlock)blocks[newID]);
				break;

			default:
				break;
			}
		}

		// 読み込んだブロックは消去
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null){
				disk.dropFile(blocks[i].getID());
			}
		}

		disk.reloadFileList();

		System.out.println("Done.");
	}

	public void saveMemory(int index){
		if(blocks[index] != null){
			// フラグを立てておく
			blocks[index].setAboutToFlush(true);
			// 既存のファイルがあれば消去
			disk.dropFile(blocks[index].getID());
			switch(blocks[index].getType()){
			case MEM_DICT:
				ReplicateDictionaryDiskBlock dicBlock = new ReplicateDictionaryDiskBlock(
						(ReplicateDictionaryBlock)blocks[index], dir);
				dicBlock.save();
				break;

			case MEM_SKIPPTR:
			case MEM_SKIPCONT:
				SkipPointerDiskBlock skipBlock = new SkipPointerDiskBlock((SkipPointerBlock)blocks[index],
						dir);
				skipBlock.save();
				break;

			case MEM_INVINDEX:
				ReplicateInvertIndexDiskBlock invBlock = new ReplicateInvertIndexDiskBlock(
						(ReplicateInvertIndexBlock)blocks[index], dir);
				invBlock.save();
				break;

			case MEM_CONT:
				ReplicateContentDiskBlock contBlock = new ReplicateContentDiskBlock(
						(ReplicateContentBlock)blocks[index], dir);
				contBlock.save();
				break;

			case MEM_PURECONT:
				PureContentDiskBlock pureBlock = new PureContentDiskBlock((PureContentBlock)blocks[index],
						dir);
				pureBlock.save();
				break;

			default:
				break;
			}
			// ブロック消去
			blocks[index] = null;
			disk.reloadFileList();
			// 最適化
			if(disk.getFileMap().size() % 200 == 0)
				new Optimizer(this, 8).optimize();
		}
	}

	public void saveMemories(){
		// 今までの記録を保存
		System.out.print("Writing to memory... ");
		for(int i = 0; i < blocks.length; i++){
			saveMemory(i);
		}

		System.out.println("Done.");
	}

	public int getMinimumBlock(int type){
		int minimum = Integer.MAX_VALUE;
		int minimumArray = -1;
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null && blocks[i].getType() == type && blocks[i].getID() < minimum){
				minimum = blocks[i].getID();
				minimumArray = i;
			}
		}

		return minimumArray;
	}

	// デバッグ用
	public void showMemoryElapsed(){
		System.out.println("--  Disk  --");
		Set<Integer> keys = disk.getFileMap().keySet();
		for(Integer diskID: keys){
			DiskBlock diskBlock;
			try{
				File desc = disk.getFileMap().get(diskID);
				diskBlock = new DiskBlock(desc){
					@Override
					public void readToMemory(MemoryBlock memBlock){
						throw new NoSuchMethodError("Unimplemented method");
					}

					@Override
					public byte[] getData(){
						throw new NoSuchMethodError("Unimplemented method");
					}
				};
				System.out.println(diskID + ": " + desc.length() + " " + diskBlock.getTypeStr());
			}catch(Exception e){}
		}

		System.out.println("-- Memory --");
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null)
				System.out.println(blocks[i].getID() + ": " + blocks[i].getUsed() + " "
						+ blocks[i].getClassName());
		}
	}

	// デバッグ用
	public void showMemoryType(){
		for(int i = 0; i < blocks.length; i++)
			if(blocks[i] != null) System.out.println(i + ": " + blocks[i].getClassName());
	}

	public void setBlockSize(int blockSize){
		this.blockSize = blockSize;
	}

	public void setBlockNum(int blockNum){
		this.blockNum = blockNum;
	}

	public int getBlockSize(){
		return blockSize;
	}

	public int getBlockNum(){
		return blockNum;
	}

	public int getContentCount() throws Exception{
		int result = 0;
		// メモリ
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null && blocks[i] instanceof ReplicateContentBlock){
				result += ((ReplicateContentBlock)blocks[i]).getCount();
			}
		}

		// ディスク
		Collection<File> files = disk.getFileMap().values();
		for(File file: files){
			ReplicateContentDiskBlock blk = new ReplicateContentDiskBlock(file);
			if(blk.getType() == MEM_CONT) result += blk.getCount();
		}

		return result;
	}

	public int getTermCount() throws Exception{
		int result = 0;
		for(int i = 0; i < blocks.length; i++){
			if(blocks[i] != null && blocks[i] instanceof ReplicateDictionaryBlock){
				int count = ((ReplicateDictionaryBlock)blocks[i]).getTermCount();
				result += count;
			}
		}

		// ディスク
		Collection<File> files = disk.getFileMap().values();
		for(File file: files){
			ReplicateDictionaryDiskBlock blk = new ReplicateDictionaryDiskBlock(file);
			if(blk.getType() == MEM_DICT) result += blk.getTermCount();
		}
		return result;
	}
	
	// ブロックNo. IDの「次」を出力
	public int isNextFile(int blockID, int type){
		int blockIndexInMemory;
		for(int i = blockID + 1; i < nextID; i++){
			blockIndexInMemory = getBlockArrayIndex(i);
			// メモリにあった
			if(blockIndexInMemory != -1 && type == blocks[blockIndexInMemory].getType())
				return i;
			else
				// ディスクにあった
				if(disk.getDiskblock(i) != null && type == disk.getDiskblock(i).getType())
					return -1 * i;
		}
		
		// そんなブロックない
		return Integer.MIN_VALUE;
	}
	
	public MemoryBlock getBlockFromMemory(int blockID){
		int index = getBlockArrayIndex(blockID);
		return blocks[index];
	}
	
	public DiskBlock getBlockFromDisk(int blockID){
		return disk.getDiskblock(blockID);
	}
	
	public String changeFileName(int blockID) {
		return disk.changeNameToBak(blockID);
	}
}
